package main.java.model;

import java.util.Observable;
import java.util.Observer;

import main.java.controller.Controller;
import main.java.model.Gears;
import main.java.model.Handle;
import main.java.view.MainFrame;

public class Application extends Observable implements Observer {
    
    //List of types of action
    private static final String [] ACTIONS = {"Close_Doors", "Open_Doors", "Retract_Gears", "Extend_Gears"};
  
    //static final variables to change them easier
    private static final int NB_ELECTROVALVES = 4;
    private static final int NB_HYDROCIRCUITS = 2;
    private static final int NB_SETS = 3;
    private static final int SLEEP = 50;
    
    //static final variables to change action id easier
    private static final int CLOSE_DOORS_ID = 0;
    private static final int OPEN_DOORS_ID = 1;
    private static final int RETRACT_GEARS_ID = 2;
    private static final int EXTEND_GEARS_ID = 3;
    
    //The view
    private MainFrame mainframe;
    //The controller
    private Controller controller; 
    
    //The different items that describe the landing gear system
    private Handle handle;
    private Lights lights;
    private Set [] sets;
    private AnalogicalSwitch analogicalSwitch;
    private static GeneralElectroValve generalElectroValve;
    private ElectroValve [] electroValve;
    private HydraulicCircuit [] hydraulicCircuit;
    
    //The ongoing action
    private String action; 
    //Counting variables
    private int countGears, countDoors; 

    public Application() {
        //Instantiation of each item
        handle = new Handle();
        lights = new Lights();
        analogicalSwitch = new AnalogicalSwitch();
        generalElectroValve = new GeneralElectroValve();
        electroValve = new ElectroValve[NB_ELECTROVALVES];
        hydraulicCircuit = new HydraulicCircuit[NB_HYDROCIRCUITS];
        sets = new Set[NB_SETS];
        
        //Instantiation of the view and the controller
        mainframe = new MainFrame();
        controller = new Controller(mainframe, handle);
        
        //Set counters to 0
        countGears = 0;
        countDoors = 0;
        
        //Instantiation of the hydraulic-circuits
        for (int i = 0; i < NB_HYDROCIRCUITS; i++) {
            hydraulicCircuit[i] = new HydraulicCircuit();
            hydraulicCircuit[i].addObserver(this);
        }
         
        //Instantiation of the electro-valves
        for (int i = 0; i < NB_ELECTROVALVES; i++) {
            electroValve[i] = new ElectroValve();
        }
        
        //Instantiation of the sets and the observers
        for (int i = 0; i < NB_SETS; i++) {
            sets[i] = new Set(i);
            
            sets[i].getDoors().addObserver(this);
            sets[i].getGears().addObserver(this);
            
            sets[i].getDoors().addObserver(mainframe.getBlocView()[i].getDoorsView());
            sets[i].getGears().addObserver(mainframe.getBlocView()[i].getGearsView());
        }
        
        //Adding observers for the other items
        handle.addObserver(this);
        lights.addObserver(this);
        lights.addObserver(mainframe.getPilotMainView().getLightsView());
        
        //Adding action listener from the controller
        mainframe.getPilotMainView().getButtonsView().getHandleButtonUp().addActionListener(controller);
        mainframe.getPilotMainView().getButtonsView().getHandleButtonDown().addActionListener(controller);
    }
    
    /**
     * The main function that launch the application
     * @param args : arguments
     */
    public static void main(String[] args) {
        Application appli = new Application();
        //Visible view
        appli.setMainFrameVisible(true);
    }
    
    /**
     * Update calling another function 
     * Depending on where comes from the change
     * @param Observable : what type of instance
     * @param Object : given change 
     */
    @Override
    public void update(Observable o, Object arg) {
        //If a change happened from the handle 
        if (o instanceof Handle) { 
            handleStep(arg);
        } else {
            //If a change happened from the gears
            if (o instanceof Gears) {
                gearsStep(arg);
            } else {
                //If a change happened from the doors
                if (o instanceof Doors) { 
                    doorsStep(arg);
                } else {
                    //If a change happened from the hydraulic circuit
                    if (o instanceof HydraulicCircuit) { 
                        hydraulicCircuitStep(arg);
                    } else {
                        //If a change happened from the lights
                        if (o instanceof Lights) { 
                            lightsStep(arg);
                        }
                    }
                }
            }
        }
    }

    /**
     * Calling the supply of the right electro-valves
     * Reset the one that was set before first
     * @param actionId : which type of action
     */
    public void setActionEValve(int actionId) {
        //Check if the analogical switch is closed
        if (analogicalSwitch.isClosed()) {
            //Set the ongoing action variable
            action = ACTIONS[actionId]; 
            
            //Reset for new electro-valves supply 
            for (ElectroValve eValve : electroValve) { 
                eValve.setE(false);
            }
            
            electroValve[actionId].setE(true);
            //Call the supply
            generalElectroValve.supply(electroValve, hydraulicCircuit); 
        }
    }
    
    /**
     * Step when a change happened from the handle
     * Calling electro-valves supply to active the mechanism
     * @param arg : the change
     */
    public void handleStep(Object arg) {

        //Check if the analogical switch is closed
        if (!analogicalSwitch.isClosed()) {
            try {
                Thread thread = new Thread(analogicalSwitch); 
                //Launch it into a thread
                thread.start(); 
                //To be sure that it is launched before the next steps
                Thread.sleep(SLEEP);
            } catch (InterruptedException e) {
            }
        }
        
        //Action depending on the change
        if (arg.toString().equals("Down")) { 
            //Calling the supply
            setActionEValve(OPEN_DOORS_ID); 
        } else {
            //Calling the supply
            setActionEValve(RETRACT_GEARS_ID); 
        }
    }
    
    /**
     * Step when a change happened from gears
     * Supply the right electro-valves
     * Or changing the color of the lights 
     * Count first the number of gears that are ready
     * @param arg : the change
     */
    public synchronized void gearsStep(Object arg) {
        //Count the number of gears ready
        countGears++;
        
        //If it is ready 
        if (countGears == sets.length) { 

            //Action depending on the change
            if (action.equals(ACTIONS[RETRACT_GEARS_ID]) && arg.toString().equals("Up")) { 
                //Calling the supply
                setActionEValve(CLOSE_DOORS_ID); 
            } else {
                //Change the color of the lights
                lights.action(arg.toString());
            }
            
            countGears = 0;
        }
    }
    
    /**
     * Step when a change happened from doors
     * Supply the right electro-valves
     * Or changing the color of the lights 
     * Count first the number of doors that are ready
     * @param arg : the change
     */
    public synchronized void doorsStep(Object arg) {
    	//If it did not fail
        if (!arg.toString().equals("Failure")) {
        	//Count the number of doors ready
            countDoors++;
        } else {
        	//Failed
            forceFailure();
        }
        
        //If it is ready
        if (countDoors == sets.length) { 
        	countDoors = 0;
        	
            //Action depending on the change
            if (arg.toString().equals("Maneuvering") || arg.toString().equals("Closed")) { 
                //Change the color of the lights
                lights.action(arg.toString()); 
            } else {
                //If doors are open
                if (arg.toString().equals("Opened")) { 
                    //Calling the supply
                    setActionEValve(EXTEND_GEARS_ID); 
                }
            }
        }
    }
    
    /**
     * Step when a change happened from the lights
     * Enable handle button
     * @param arg : the change
     */
    public void lightsStep(Object arg) {
    	//Enable the button down
    	if (arg.toString().equals("Green")) { 
    		mainframe.getPilotMainView().getButtonsView().getHandleButtonDown().setEnabled(true);
    	} else {
    		//Enable the button up
    		if (arg.toString().equals("White")) { 
    			mainframe.getPilotMainView().getButtonsView().getHandleButtonUp().setEnabled(true);
        	}
    	}
    }
    
    /**
     * Action that put into threads the doors mechanism
     * Each door is simulated by a thread
     * @param arg : the change
     */
    public void threadDoors(Object arg) {
        Thread one = new Thread() {
            public void run() {
                //For the front door
                sets[0].getDoors().action(arg.toString()); 
            }
        };
        Thread two = new Thread() {
            public void run() {
                //For the left door
                sets[1].getDoors().action(arg.toString()); 
            }
        };
        Thread three = new Thread() {
            public void run() {
                //For the right door
                sets[2].getDoors().action(arg.toString()); 
            }
        };
        one.start();
        two.start();
        three.start();
    }
    
    /**
     * Action that put into threads the gears mechanism
     * Each door is simulated by a thread
     * @param arg : the change
     */
    public void threadGears(Object arg) {
        Thread one = new Thread() {
            public void run() {
                sets[0].getGears().action(arg.toString());
            }
        };
        Thread two = new Thread() {
            public void run() {
                sets[1].getGears().action(arg.toString());
            }
        };
        Thread three = new Thread() {
            public void run() {
                sets[2].getGears().action(arg.toString());
            }
        };
        one.start();
        two.start();
        three.start();
    }
    
    /**
     * Step when a change happened from hydraulic circuit
     * Calling the right function
     * Depending on the type of action that is 
     * @param arg : the change
     */
    public void hydraulicCircuitStep(Object arg) {

        //Closing doors action
        if (action.equals(ACTIONS[CLOSE_DOORS_ID])) { 
            threadDoors(arg);
        } else {
            //Opening doors action
            if (action.equals(ACTIONS[OPEN_DOORS_ID])) { 
                threadDoors(arg);
            } else {
                //Retracting gears action
                if (action.equals(ACTIONS[RETRACT_GEARS_ID])) { 
                    threadGears(arg);
                } else {
                    //Extending gears action
                    if (action.equals(ACTIONS[EXTEND_GEARS_ID])) { 
                        threadGears(arg);
                    }
                }
            }
        }
    }
    
    /**
     * Failed door action
     * Activate manually the doors and gears
     */
    public void forceFailure() {
    	mainframe.getPilotMainView().getButtonsView().getHandleButtonUp().setText("Manually");
    	mainframe.getPilotMainView().getButtonsView().getHandleButtonUp().setEnabled(true);
    	mainframe.getPilotMainView().getLightsView().switchImage("Red");
    }
    
    /**
     * Set Visible MainFrame
     * @param visible : boolean
     */
    public void setMainFrameVisible(boolean visible) {
    	mainframe.setVisible(true);
    }

	/**
	 * @return the handle
	 */
	public Handle getHandle() {
		return handle;
	}

	/**
	 * @return the lights
	 */
	public Lights getLights() {
		return lights;
	}

	/**
	 * @return the sets
	 */
	public Set[] getSets() {
		return sets;
	}

	/**
	 * @return the analogicalSwitch
	 */
	public AnalogicalSwitch getAnalogicalSwitch() {
		return analogicalSwitch;
	}

	/**
	 * @return the electroValve
	 */
	public ElectroValve[] getElectroValve() {
		return electroValve;
	}

	/**
	 * @return the hydraulicCircuit
	 */
	public HydraulicCircuit[] getHydraulicCircuit() {
		return hydraulicCircuit;
	}

	/**
	 * @return the countGears
	 */
	public int getCountGears() {
		return countGears;
	}

	/**
	 * @return the countDoors
	 */
	public int getCountDoors() {
		return countDoors;
	}

	/**
	 * @param handle the handle to set
	 */
	public void setHandle(Handle handle) {
		this.handle = handle;
	}

	/**
	 * @param lights the lights to set
	 */
	public void setLights(Lights lights) {
		this.lights = lights;
	}

	/**
	 * @param sets the sets to set
	 */
	public void setSets(Set[] sets) {
		this.sets = sets;
	}

	/**
	 * @param analogicalSwitch the analogicalSwitch to set
	 */
	public void setAnalogicalSwitch(AnalogicalSwitch analogicalSwitch) {
		this.analogicalSwitch = analogicalSwitch;
	}

	/**
	 * @param electroValve the electroValve to set
	 */
	public void setElectroValve(ElectroValve[] electroValve) {
		this.electroValve = electroValve;
	}

	/**
	 * @param hydraulicCircuit the hydraulicCircuit to set
	 */
	public void setHydraulicCircuit(HydraulicCircuit[] hydraulicCircuit) {
		this.hydraulicCircuit = hydraulicCircuit;
	}

	/**
	 * @param countGears the countGears to set
	 */
	public void setCountGears(int countGears) {
		this.countGears = countGears;
	}

	/**
	 * @param countDoors the countDoors to set
	 */
	public void setCountDoors(int countDoors) {
		this.countDoors = countDoors;
	}

	/**
	 * @return the action
	 */
	public String getAction() {
		return action;
	}

	/**
	 * @param action the action to set
	 */
	public void setAction(String action) {
		this.action = action;
	}
}
